---
title: React Navigation
description: Navigate to other screens from within the bottom sheet.
keywords: [bottom sheet navigation, react-navigation, navigating from sheet, sheet navigator]
---

import navigation from './navigation.gif'

TrueSheet integrates with React Navigation out of the box. It just works! 😉

<img alt="navigation" src={navigation} width="300"/>

## How?

You can use the [Sheet Navigator](#sheet-navigator) to present screens as sheets, or simply [navigate from within sheets](#navigating-from-sheets) using your existing navigation setup.

## Sheet Navigator

TrueSheet provides a custom navigator for React Navigation. The first screen (or `initialRouteName`) is the base content, while other screens are presented as sheets.

```bash
npm install @react-navigation/native
```

### Basic Usage

```tsx
import { NavigationContainer } from '@react-navigation/native';
import {
  createTrueSheetNavigator,
  useTrueSheetNavigation,
} from '@lodev09/react-native-true-sheet/navigation';

const Sheet = createTrueSheetNavigator();

function App() {
  return (
    <NavigationContainer>
      <Sheet.Navigator>
        {/* Base screen (first screen is the default) */}
        <Sheet.Screen name="Main" component={MainScreen} />
        {/* Sheet screens */}
        <Sheet.Screen
          name="Details"
          component={DetailsSheet}
          options={{ detents: ['auto', 1], cornerRadius: 16 }}
        />
      </Sheet.Navigator>
    </NavigationContainer>
  );
}
```

### Wrapping Existing Navigation

Wrap your root navigator to present sheets from anywhere:

```tsx
const Stack = createNativeStackNavigator();
const Sheet = createTrueSheetNavigator();

function RootStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Profile" component={ProfileScreen} />
    </Stack.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <Sheet.Navigator>
        <Sheet.Screen name="Root" component={RootStack} />
        <Sheet.Screen
          name="Details"
          component={DetailsSheet}
          options={{ detents: ['auto', 1], cornerRadius: 16 }}
        />
      </Sheet.Navigator>
    </NavigationContainer>
  );
}
```

### Navigation & Resizing

```tsx
function DetailsSheet() {
  const navigation = useTrueSheetNavigation();

  return (
    <View>
      <Button title="Expand" onPress={() => navigation.resize(1)} />
      <Button title="Close" onPress={() => navigation.goBack()} />
    </View>
  );
}
```

### Screen Options

All [TrueSheet props](/reference/configuration) are available as screen options, plus the following navigation-specific options:

| Option | Type | Description |
|--------|------|-------------|
| `detentIndex` | `number` | The detent index to present at. Defaults to `0`. |
| `reanimated` | `boolean` | Enable worklet-based position events for this screen. |
| `positionChangeHandler` | `function` | A callback that receives position change events. When `reanimated` is enabled, this must be a worklet function. |

### Reanimated Integration

Enable worklet-based position events for smooth UI thread animations:

```tsx
// In your navigator
<Sheet.Screen
  name="Details"
  component={DetailsSheet}
  options={{
    reanimated: true,
    positionChangeHandler: (payload) => {
      'worklet';
      // Access payload.position, payload.detentIndex, etc.
      console.log(payload.position);
    },
  }}
/>
```

:::note
When `reanimated: true` is set, `react-native-reanimated` must be installed and `positionChangeHandler` must be a worklet function. The integration is lazy-loaded, so screens without `reanimated: true` don't require reanimated.
:::

### Screen Listeners

```tsx
<Sheet.Navigator
  screenListeners={{
    sheetDidPresent: (e) => console.log('Presented:', e.data.index),
    sheetDidDismiss: () => console.log('Dismissed'),
  }}
>
```

| Event | Description |
|-------|-------------|
| `sheetWillPresent` | Sheet is about to present |
| `sheetDidPresent` | Sheet finished presenting |
| `sheetWillDismiss` | Sheet is about to dismiss |
| `sheetDidDismiss` | Sheet finished dismissing |
| `sheetDetentChange` | Detent changed |
| `sheetDragBegin` | User started dragging |
| `sheetDragChange` | User is dragging |
| `sheetDragEnd` | User stopped dragging |
| `sheetPositionChange` | Position changed |

### Expo Router

```
app/
├── _layout.tsx          # TrueSheet navigator
├── (main)/              # Base content
│   ├── _layout.tsx      # Stack or Tabs
│   └── index.tsx
└── details.tsx          # Sheet screen
```

```tsx
// app/_layout.tsx
import { withLayoutContext } from 'expo-router';
import { createTrueSheetNavigator } from '@lodev09/react-native-true-sheet/navigation';

const { Navigator } = createTrueSheetNavigator();
export default withLayoutContext(Navigator);
```

```tsx
// app/details.tsx
export const unstable_settings = {
  options: { detents: ['auto', 1], cornerRadius: 16 },
};

export default function DetailsSheet() {
  const navigation = useTrueSheetNavigation();
  return (
    <View>
      <Button title="Close" onPress={() => navigation.goBack()} />
    </View>
  );
}
```

## Navigating from Sheets

:::note
Requires a [patch to react-native-screens](https://github.com/lodev09/react-native-true-sheet/blob/main/.yarn/patches/react-native-screens-npm-4.18.0-fa7de65975.patch). See [PR #3415](https://github.com/software-mansion/react-native-screens/pull/3415).
:::

Navigate directly from sheets - they remain visible when presenting modals on top.

```tsx
// Navigate directly - no need to dismiss first!
navigation.navigate('SomeScreen')
```

### Focus Events

Respond when sheets lose/regain focus due to modals:

```tsx
<TrueSheet
  onDidBlur={() => console.log('Lost focus')}
  onDidFocus={() => console.log('Regained focus')}
>
```

### Presenting on Screen Focus

When using `useFocusEffect`, delay presentation to avoid iOS issues:

```tsx
useFocusEffect(
  useCallback(() => {
    requestAnimationFrame(() => {
      sheet.current?.present()
    })
  }, [])
)
```

### Present During Mount

When using `initialDetentIndex` with animation, the sheet may behave unexpectedly during screen transitions.

**Solution 1:** Disable animation

```tsx
<TrueSheet initialDetentIndex={0} initialDetentAnimated={false}>
```

**Solution 2:** Wait for transition

```tsx
const [ready, setReady] = useState(false)

useEffect(() => {
  const unsubscribe = navigation.addListener("transitionEnd", ({ data }) => {
    if (!data.closing) setReady(true)
  })
  return unsubscribe
}, [])

if (!ready) return null

return <TrueSheet initialDetentIndex={0}>{/* ... */}</TrueSheet>
```
